import { CsaUtils, csa } from "./utils";

type CsaFrameEl = HTMLIFrameElement | HTMLFrameElement;

/** Csa 主站配置 */
interface CsaMasterOption {
  /** Csa 主站框架 */
  el?: string | CsaFrameEl;
  /** Csa 主站域名 --> targetOrigin */
  frameOrigin?: string;
}

/** Csa 主站状态 */
class CsaMasterStatus<T = number> {
  /**
   * Csa 主站状态
   * @param name 状态名称
   * @param code 状态代码
   */
  public constructor(public readonly name: string, public readonly code: T) { }
  /** 待加载 */
  public static readonly unload = new CsaMasterStatus("unload", 0);
  /** 连接中... */
  public static readonly connecting = new CsaMasterStatus("connecting", 1);
  /** 已连接 */
  public static readonly connected = new CsaMasterStatus("connected", 2);
}


/** Csa 主站访问器 */
class CsaMasterAccessor {
  private frameEl?: CsaFrameEl
  private readonly channel: MessageChannel;
  private readonly targetOrigin: string;

  private readonly listens: { [key: csa.CsaFrameEvent]: { once: boolean, listener: csa.CsaFrameListener }[] } = {};
  private readonly returnHandlers: Record<string, Function> = {};
  private exposeCtx: Record<csa.CsaFrameCsaCallName, Function> = {};

  private status = CsaMasterStatus.unload;

  set frame(el: string | CsaFrameEl) {
    const frame = typeof el === 'string'
      ? document.querySelector(el)
      : el;
    if (!(frame instanceof HTMLIFrameElement || frame instanceof HTMLFrameElement)) return;
    this.frameEl = frame;
    this.frameEl.addEventListener("load", () => {
      this.status = CsaMasterStatus.connecting;
      frame.contentWindow?.postMessage("connecting", this.targetOrigin, [this.channel.port2]);
    }, false);
  }

  /** 设置导出函数 */
  set expose(ctx: Record<string, Function>) {
    this.exposeCtx = ctx || {};
  }

  /** 主站发送对象 */
  private get port() {
    return this.channel.port1;
  }

  /** 已连接 */
  public get connected() {
    if (!this.frameEl) return false;
    return this.status === CsaMasterStatus.connected;
  }

  /**
   * Csa 主站构造器
   * @param config 访问器配置
   */
  constructor(config?: CsaMasterOption) {
    if (typeof config?.el !== 'undefined') this.frame = config?.el;
    this.targetOrigin = config?.frameOrigin ?? '*';
    this.channel = new MessageChannel();
    this.channel.port1.onmessage = (event: MessageEvent<csa.CsaMessage | 'connected'>) => {
      if (event.data === 'connected') {
        this.status = CsaMasterStatus.connected;
        CsaUtils.nextTick(() => {
          const listens = (this.listens.ready || []);
          for (const listen of listens) {
            CsaUtils.tryApply(listen.listener);
          }
        });
        return;
      }

      const { csaEvent } = event.data;
      // 从站调用主站返回值回调
      if (CsaUtils.EVENT_CALL_RETURN === csaEvent) {
        const { csaId, csaIsError, csaReturnValue } = event.data;
        return CsaUtils.nextTick(() => {
          if (typeof csaId !== 'string') return;

          const handler = this.returnHandlers[csaId];
          if (typeof handler !== 'function') return;
          const value = csaIsError ? new Error(`[call-error]` + csaReturnValue)
            : csaReturnValue;
          handler(value);
        });
      }

      // 主站调用从站
      if (CsaUtils.EVENT_CALL === csaEvent) {
        const { csaId, csaFnName, csaFnArgs } = event.data;
        return CsaUtils.nextTick(async () => {
          const method = this.exposeCtx[csaFnName];
          try {
            const value = await method(...csaFnArgs);
            const message: csa.CsaCallReturnMessage = {
              csaSource: CsaUtils.SOURCE_FRAME,
              csaOrigin: location.origin,
              csaEvent: CsaUtils.EVENT_CALL_RETURN,
              csaId,
              csaReturnValue: value
            };
            this.port.postMessage(message);
          } catch (error) {
            const message: csa.CsaCallReturnMessage = {
              csaSource: CsaUtils.SOURCE_FRAME,
              csaOrigin: location.origin,
              csaEvent: CsaUtils.EVENT_CALL_RETURN,
              csaId,
              csaIsError: true,
              csaReturnValue: (error as any).message
            };
            this.port.postMessage(message);
          }
        });
      }

      if (CsaUtils.EVENT_EMIT === csaEvent) {
        const { csaEmitName, csaEmitArgs } = event.data;
        CsaUtils.nextTick(() => {
          const listens = (this.listens[csaEmitName] || []);
          for (const listen of listens) {
            CsaUtils.tryApply(listen.listener, csaEmitArgs, listen);
          }
        });
        return;
      }
      console.warn(`[CSA/FRAME/MESSAGE]NOT SUPPORT`, event);
    }
  }

  /**
  * 调用主站导出方法
  * @param name 主站导出方法名称
  * @param args 主站导出方法参数值
  * @returns 主站导出方法调用返回值
  */
  async call<T = any | void>(name: csa.CsaMasterCsaCallName, ...args: any[]): Promise<T> {
    if (!this.connected) throw new Error('Csa 未连接');
    return new Promise<T>(resolve => {
      const message: csa.CsaCallMessage = {
        csaId: `${name}_${new Date().getTime().toString(16)}`,
        csaSource: CsaUtils.SOURCE_FRAME,
        csaOrigin: location.origin,
        csaEvent: CsaUtils.EVENT_CALL,
        csaFnName: name,
        csaFnArgs: args
      };
      // 添加返回函数
      this.returnHandlers[message.csaId!] = resolve;
      this.port.postMessage(message);
    });
  }

  /**
   * 发送/触发从站消息
   * @param destination 发送/触发目标
   * @param messagePayload 消息负载信息
   */
  emit(destination: csa.CsaMasterFrameEvent, ...messagePayload: any[]) {
    if (!this.connected) throw new Error('Csa 未连接');
    const message: csa.CsaEmitMessage = {
      csaSource: CsaUtils.SOURCE_FRAME,
      csaOrigin: location.origin,
      csaEvent: CsaUtils.EVENT_EMIT,
      csaEmitName: destination,
      csaEmitArgs: messagePayload
    };
    this.port.postMessage(message);
  }

  /**
   * 监听/订阅主站消息
   * @param destination 订阅/监听目标
   * @param listener  订阅/监听消息处理函数
   * @param once 是否一次性，默认false,永久监听
   * @returns 停止订阅/监听处理函数
   */
  on(type: csa.CsaFrameEvent, listener: csa.CsaFrameListener, once = false): csa.CsaStopFrameListenHandler {
    if (this.connected) {
      Promise.resolve(() => listener())
      if (once) return () => { };
    }
    const listen = { once, listener };
    return () => {
      const index = this.listens[type].indexOf(listen);
      if (index >= 0) this.listens[type].splice(index, 0);
    }
  }

  /**
   * 监听Csa是否已加载[允许多次监听]
   */
  ready(): Promise<void>;
  /**
   * 监听Csa是否已加载[允许多次监听]
   * @param handler 已加载的处理函数
   */
  ready(handler: csa.FrameReadyListener): void;
  ready(handler?: csa.FrameReadyListener): void | Promise<void> {
    if (typeof handler === 'undefined') {
      return new Promise<void>(resolve => this.on('ready', resolve));
    }
    this.on('ready', handler, true);
  }


  /** 创建Csa跨站-从站对象 */
  public static create(config?: CsaMasterOption) {
    return new CsaMasterAccessor(config);
  }
}

export default CsaMasterAccessor;
export { CsaMasterAccessor };
export const createMasterAccessor = CsaMasterAccessor.create;
